﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

using Emgu.CV;
using System.Drawing;
using SLRT.dao.data;
using System.Drawing.Imaging;

using System.ComponentModel;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

using System.Collections;

namespace SLRT.business.src
{
    class ImageAlgorithms
    {
        private static int MIN_AREA_FOR_A_DECENT_SHAPE = 150;

        public static int[,] toResizedGreyscaleMatrix(IImage source, int topLeftX, int topLeftY, int bottomRightX, int bottomRightY, int destinationWidth, int destinationHeight)
        {

            if ((source == null) || (destinationWidth <= 0) || (destinationHeight <= 0))
            {
                return null;
            }

            if ((topLeftX >= source.Bitmap.Width)
                || (topLeftX < 0) || (topLeftY < 0)
                || (topLeftY >= source.Bitmap.Height) || (bottomRightX >= source.Bitmap.Width)
                || (bottomRightX < 0) || (bottomRightY < 0)
                || (bottomRightY >= source.Bitmap.Height)
                || (topLeftX >= bottomRightX) || (topLeftY >= bottomRightY))
            {
                return null;
            }

            int[,] dIm = new int[destinationHeight, destinationWidth];
            int sourceXInt, sourceYInt, destinationXInt, destinationYInt;
            double dx, dy, sourceXDouble, sourceYDouble;

            dx = (double)(bottomRightX - topLeftX) / destinationWidth;
            dy = (double)(bottomRightY - topLeftY) / destinationHeight;

            sourceYDouble = topLeftY;
            sourceYInt = topLeftY;
            destinationYInt = 0;
            sourceXInt = 0;
            while (destinationYInt < destinationHeight)
            {
                sourceXDouble = topLeftX;
                sourceXInt = topLeftX;
                destinationXInt = 0;
                while (destinationXInt < destinationWidth)
                {
                    Color sourceColor = source.Bitmap.GetPixel(sourceXInt, sourceYInt);
                    int destColor = (sourceColor.R + sourceColor.G + sourceColor.B) / 3;
                    dIm[destinationYInt, destinationXInt] = destColor;

                    sourceXDouble += dx;
                    sourceXInt = (int)sourceXDouble;
                    destinationXInt++;
                }
                sourceYDouble += dy;
                sourceYInt = (int)sourceYDouble;
                destinationYInt++;
            }

            return dIm;
        }

        public static int[,] bitmapToResizedGreyscaleMatrix(byte[] source, int sourceWidth, int sourceHeight, int topLeftX, int topLeftY, int bottomRightX, int bottomRightY, int destinationWidth, int destinationHeight)
        {

            if ((source == null) || (destinationWidth <= 0) || (destinationHeight <= 0))
            {
                return null;
            }

            if ((topLeftX >= sourceWidth)
                || (topLeftX < 0) || (topLeftY < 0)
                || (topLeftY >= sourceHeight) || (bottomRightX >= sourceWidth)
                || (bottomRightX < 0) || (bottomRightY < 0)
                || (bottomRightY >= sourceHeight)
                || (topLeftX >= bottomRightX) || (topLeftY >= bottomRightY))
            {
                return null;
            }

            int[,] dIm = new int[destinationHeight, destinationWidth];
            int sourceXInt, sourceYInt, destinationXInt, destinationYInt;
            double dx, dy, sourceXDouble, sourceYDouble;

            dx = (double)(bottomRightX - topLeftX) / destinationWidth;
            dy = (double)(bottomRightY - topLeftY) / destinationHeight;

            sourceYDouble = topLeftY;
            sourceYInt = topLeftY;
            destinationYInt = 0;
            sourceXInt = 0;
            while (destinationYInt < destinationHeight)
            {
                sourceXDouble = topLeftX;
                sourceXInt = topLeftX;
                destinationXInt = 0;
                while (destinationXInt < destinationWidth)
                {
                    int address = 3 * (sourceYInt * sourceWidth + sourceXInt);
                    byte r = source[address];
                    byte g = source[address + 1];
                    byte b = source[address + 2];
                    int destColor = (r + g + b) / 3;
                    dIm[destinationYInt, destinationXInt] = r;

                    sourceXDouble += dx;
                    sourceXInt = (int)sourceXDouble;
                    destinationXInt++;
                }
                sourceYDouble += dy;
                sourceYInt = (int)sourceYDouble;
                destinationYInt++;
            }

            return dIm;
        }

        /*
         * Unsafe fast motherfucka!!!
         */
        public unsafe static byte[] BmpToBytes_Unsafe(Bitmap bmp)
        {
            System.Drawing.Imaging.BitmapData bData = bmp.LockBits(new Rectangle(new Point(), bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            // number of bytes in the bitmap
            int byteCount = bData.Stride * bmp.Height;
            byte[] bmpBytes = new byte[byteCount];

            // Copy the locked bytes from memory
            Marshal.Copy(bData.Scan0, bmpBytes, 0, byteCount);

            // don't forget to unlock the bitmap!!
            bmp.UnlockBits(bData);

            return bmpBytes;
        }

        /**
         * Creates a contour image and computes the histogram of a greyscale matrix.
         */
        public static int toContour(int[,] source, int radius, ref int[,] contour, ref int[] histogram)
        {
            if ((source == null) || (radius <= 0))
            {
                return -1;
            }

            int width = source.Length / source.GetLength(0), height = source.GetLength(0);
            //move out int[,] contour = new int[height, width];
            //int[] histogram = new int[256];
            int[] neighX = new int[2 * (radius * (radius + 1))], neighY = new int[2 * (radius * (radius + 1))];
            int sourceGray, neighbourGray;
            int x, y, nx, ny, i, j, diff;
            int diam = radius * 2 + 1, dist;

            /**
             * Preprocessing part.
             * Creating an array about the neighbours' positions.
             * {[0, -1], [-1, 0], [1, 0], [0, 1]} for radius of 1.
             */
            j = 0;
            for (y = 0; y < diam; y++)
            {
                for (x = 0; x < diam; x++)
                {
                    dist = Math.Abs(radius - x) + Math.Abs(radius - y);
                    if ((dist > 0) && (dist <= radius))
                    {
                        neighX[j] = x - radius;
                        neighY[j] = y - radius;
                        j++;
                    }
                }
            }

            for (i = 0; i < 255; i++)
            {
                histogram[i] = 0;
            }

            /**
             * For every pixel computes the average difference to it's neighbours'
             * gray level and by this creates the contour matrix.
             * In the same time creates the histogram of that contour matrix.
             */            
            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    sourceGray = source[y, x];
                    diff = 0;
                    j = 0;

                    for (i = 0; i < neighX.Length; i++)
                    {
                        nx = x + neighX[i];
                        ny = y + neighY[i];
                        if ((nx >= 0) && (nx < width) && (ny < height) && (ny >= 0))
                        {
                            neighbourGray = source[ny, nx];
                            diff += (int) Math.Abs(sourceGray - neighbourGray);
                            j++;
                        }
                    }
                    diff /= j;

                    contour[y, x] = diff;
                    histogram[diff]++;
                }
            }
            return 1;
        }

        /**
         * Computing a threshold so that when we threshold a greyscale
         * image into a black and white image, the ratio between the number
         * of black pixels and white pixels will be equal to @BWRatio.
         */
        public static int computeNecessaryThreshold(double BWRatio, int[] histogram, int area)
        {
            if ((BWRatio < 0.0) || (BWRatio > 1.0) || (histogram == null) || (area < 0))
            {
                return -1;
            }

            int limit = (int)Math.Round(area * BWRatio);
            double sum = 0;
            int threshold = 255;
            do
            {
                threshold--;
                sum += histogram[threshold];
            } while ((threshold > 0) && (sum <= limit));

            return threshold;
        }

        /**
         * Converting a greyscale image to pure black and white.
         */
        public static bool[,] toBoolImage(int[,] source, int threshold)
        {
            if ((source == null) || (threshold <= 0))
            {
                return null;
            }

            int width = source.GetLength(0), height = source.Length;
            bool[,] boolImage = new bool[height, width];
            int sGray;
            int x, y;

            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    sGray = source[y, x];

                    if (sGray < threshold)
                    {
                        boolImage[y, x] = false;
                    }
                    else
                    {
                        boolImage[y, x] = true;
                    }
                }
            }

            return boolImage;
        }

        /**
         * Finds the greatest contiguos shape in the boolean image.
         */
        public static Shape findGreatestShape(bool[,] source)
        {
            if (source == null)
            {
                return null;
            }

            int height = source.GetLength(0), width = source.Length / source.GetLength(0);
            bool[,] workingIm = new bool[height, width];
            int x, y;

            /**
             * Just copying to another instance.
             */
            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    workingIm[y, x] = source[y, x];
                }
            }


            Shape greatestShape = null;
            int maxArea = 0;
            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    if (workingIm[y, x])
                    {
                        Shape currentShape = ImageAlgorithms.breadthShapeExtendAndCut(workingIm, new Point(x, y));
                        int currentArea = currentShape.Area;
                        if (maxArea < currentArea)
                        {
                            maxArea = currentArea;
                            greatestShape = currentShape;
                        }
                    }
                }
            }

            return greatestShape;
        }

        /**
         * Finds the n greatest contiguos shapes in the boolean image.
         */
        public static List<Shape> extractAllShapes(bool[,] source)
        {
            if (source == null)
            {
                return null;
            }

            int height = source.GetLength(0), width = source.Length / source.GetLength(0);
            
            bool[,] workingIm = new bool[height, width];
            //source.CopyTo(workingIm, 0);            

            int x, y;

            /**
             * Just copying to another instance.
             */
            
            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    workingIm[y, x] = source[y, x];
                }
            }
            


            List<Shape> shapes = new List<Shape>();            
            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    if (workingIm[y, x])
                    {
                        Shape currentShape = ImageAlgorithms.breadthShapeExtendAndCut(workingIm, new Point(x, y));
                        if (currentShape.Area > MIN_AREA_FOR_A_DECENT_SHAPE)
                        {
                            shapes.Add(currentShape);
                        }
                    }
                }
            }

            shapes.Sort();
            shapes.Reverse();

            return shapes;
        }

        public static Shape MergeShapes(List<Shape> shapes, int n)
        {
            if (shapes.Count == 0) {
                return null;
            }
            else if (shapes.Count == 1 && shapes[0].Area < MIN_AREA_FOR_A_DECENT_SHAPE)
            {
                return null;
            }
             
            for (int i = 1; i < n && i < shapes.Count; i++)
            {
                if (shapes[i].Area > MIN_AREA_FOR_A_DECENT_SHAPE)
                {
                    shapes[0].Add(shapes[i]);
                }
            }
             
            return shapes[0];
        }

        /**
         * Finds a contiguos shape in the boolean image by doing breadth first search.
         * It also deletes the found pixels from the source image, cutting the shape out from the original.
         */
        public static Shape breadthShapeExtendAndCut(bool[,] source, Point? firstPoint)
        {
            int i;
            int height = source.GetLength(0), width = source.Length / source.GetLength(0);
            int px, py, nx, ny;
            Point? head, neighbour;
            Queue<Point?> queue;
            int[] neighx = { -1, 0, 1, -1, 1, -1, 0, 1 }, neighy = { -1, -1, 1, 0, 0, 1, 1, 1 };

            Shape shape = new Shape();
            queue = new Queue<Point?>();
            queue.Enqueue(firstPoint);
            while (queue.Count > 0)
            {
                head = queue.Dequeue();
                shape.Add(head);
                px = head.Value.X;
                py = head.Value.Y;
                source[py, px] = false;
                for (i = 0; i < 8; i++)
                {
                    nx = px + neighx[i];
                    ny = py + neighy[i];
                    neighbour = new Point(nx, ny);
                    if ((nx >= 0) && (nx < width) && (ny >= 0) && (ny < height)
                            && (source[ny, nx])
                            && (!shape.contains(neighbour)) && (!queue.Contains(neighbour)))
                    {
                        queue.Enqueue(neighbour);
                        source[ny, nx] = false;
                    }
                }
            }

            return shape;
        }

        /**
         * Extracts a horizontal or vertical shape of the original Shape.
         * The area of the new, smaller flat shape will be smaller thatn the areaLimit.
         * sizeLimit specifies the width of the flat, extracted shape.
         */
        public static Shape reduceShapeHVAreaLimit(Shape originalShape, Point? firstPoint, int sizeLimit, bool horizontal, int areaLimit)
        {
            int px, py, nx, ny;
            int[] neighx = { -1, 0, 1, -1, 1, -1, 0, 1 }, neighy = { -1, -1, 1, 0, 0, 1, 1, 1 };

            Shape reducedShape = new Shape();
            Queue<Point?> queue = new Queue<Point?>();
            if (originalShape == null)
            {
                return null;
            }
            if (!originalShape.contains(firstPoint))
            {
                return null;
            }
            queue.Enqueue(firstPoint);
            while (true)
            {
                Point? head = queue.Dequeue();
                reducedShape.Add(head);
                int reducedArea = reducedShape.Area;
                Point? shapeCenter = reducedShape.getCenter();
                px = head.Value.X;
                py = head.Value.Y;
                for (int i = 0; i < 8; i++)
                {
                    nx = px + neighx[i];
                    ny = py + neighy[i];
                    Point? neighbour = new Point(nx, ny);
                    if ((originalShape.contains(neighbour))
                            && (!reducedShape.contains(neighbour)) && (!queue.Contains(neighbour))
                            && (queue.Count() + reducedArea < areaLimit)
                            && (((horizontal) && (Math.Abs(nx - shapeCenter.Value.X) < sizeLimit))
                            || ((!horizontal) && (Math.Abs(ny - shapeCenter.Value.Y) < sizeLimit)))
                            )
                    {
                        queue.Enqueue(neighbour);
                    }
                }

                if ((reducedArea > areaLimit) || (queue.Count == 0))
                {
                    break;
                }
            }

            return reducedShape;
        }


        /**
         * 
         * 
         * (Că mâna lui Iulia e mai groooasă. Numai de aia e făcută funcţia asta.)
         */
        public static bool[,] translateAndScale(bool[,] source, int translateX, int translateY, double scaleX, double scaleY, int cx, int cy)
        {

            int height = source.GetLength(0), width = source.Length / source.GetLength(0);
            bool[,] destination = new bool[height, width];
            int sourceXInt, sourceYInt, destinationXInt, destinationYInt;
            double dx, dy, sourceXDouble, sourceYDouble;

            //wtf z this
            scaleX = 1 / scaleX;
            scaleY = 1 / scaleY;

            // down and right
            sourceYDouble = cy;
            sourceYInt = cy;
            destinationYInt = cy + translateY;
            while ((destinationYInt >= 0) && (destinationYInt < height))
            {
                sourceXDouble = cx;
                sourceXInt = cx;
                destinationXInt = cx + translateX;
                while ((destinationXInt >= 0) && (destinationXInt < width))
                {
                    if ((sourceXInt >= 0) && (sourceXInt < width) && (sourceYInt >= 0) && (sourceYInt < height))
                    {
                        destination[destinationYInt, destinationXInt] = source[sourceYInt, sourceXInt];
                    }
                    else
                    {
                        destination[destinationYInt, destinationXInt] = false;
                    }

                    sourceXDouble += scaleX;
                    sourceXInt = (int)sourceXDouble;
                    destinationXInt++;
                }
                sourceYDouble += scaleY;
                sourceYInt = (int)sourceYDouble;
                destinationYInt++;
            }

            // down and left
            sourceYDouble = cy;
            sourceYInt = cy;
            destinationYInt = cy + translateY;
            while ((destinationYInt >= 0) && (destinationYInt < height))
            {
                sourceXDouble = cx;
                sourceXInt = cx;
                destinationXInt = cx + translateX;
                while ((destinationXInt >= 0) && (destinationXInt < width))
                {
                    if ((sourceXInt >= 0) && (sourceXInt < width) && (sourceYInt >= 0) && (sourceYInt < height))
                    {
                        destination[destinationYInt, destinationXInt] = source[sourceYInt, sourceXInt];
                    }
                    else
                    {
                        destination[destinationYInt, destinationXInt] = false;
                    }

                    sourceXDouble -= scaleX;
                    sourceXInt = (int)(sourceXDouble + 1.0);
                    destinationXInt--;
                }
                sourceYDouble += scaleY;
                sourceYInt = (int)sourceYDouble;
                destinationYInt++;
            }

            // up and right
            sourceYDouble = cy;
            sourceYInt = cy;
            destinationYInt = cy + translateY;
            while ((destinationYInt >= 0) && (destinationYInt < height))
            {
                sourceXDouble = cx;
                sourceXInt = cx;
                destinationXInt = cx + translateX;
                while ((destinationXInt >= 0) && (destinationXInt < width))
                {
                    if ((sourceXInt >= 0) && (sourceXInt < width) && (sourceYInt >= 0) && (sourceYInt < height))
                    {
                        destination[destinationYInt, destinationXInt] = source[sourceYInt, sourceXInt];
                    }
                    else
                    {
                        destination[destinationYInt, destinationXInt] = false;
                    }

                    sourceXDouble += scaleX;
                    sourceXInt = (int)sourceXDouble;
                    destinationXInt++;
                }
                sourceYDouble -= scaleY;
                sourceYInt = (int)(sourceYDouble + 1.0);
                destinationYInt--;
            }

            // up and left
            sourceYDouble = cy;
            sourceYInt = cy;
            destinationYInt = cy + translateY;
            while ((destinationYInt >= 0) && (destinationYInt < height))
            {
                sourceXDouble = cx;
                sourceXInt = cx;
                destinationXInt = cx + translateX;
                while ((destinationXInt >= 0) && (destinationXInt < width))
                {
                    if ((sourceXInt >= 0) && (sourceXInt < width) && (sourceYInt >= 0) && (sourceYInt < height))
                    {
                        destination[destinationYInt, destinationXInt] = source[sourceYInt, sourceXInt];
                    }
                    else
                    {
                        destination[destinationYInt, destinationXInt] = false;
                    }

                    sourceXDouble -= scaleX;
                    sourceXInt = (int)(sourceXDouble + 1.0);
                    destinationXInt--;
                }
                sourceYDouble -= scaleY;
                sourceYInt = (int)(sourceYDouble + 1.0);
                destinationYInt--;
            }

            return destination;
        }

        /**
         * Logical AND between two boolean images.
         */
        public static double compareTwoBooleanImages(bool[,] imageRed, bool[,] imageBlue)
        {
            if ((imageRed == null) || (imageBlue == null))
            {
                Console.Write("compare2BoolIms() failed1\n");
                return 0.0;
            }
            if ((imageRed[0,0] == null) || (imageBlue[0,0] == null))
            {
                Console.Write("compare2BoolIms() failed2\n");
                return 0.0;
            }
            if ((imageRed.Length != imageBlue.Length) || (imageRed.GetLength(0) != imageBlue.GetLength(0)))
            {
                Console.Write("compare2BoolIms() failed3\n");
                return 0.0;
            }

            int he = imageRed.GetLength(0), wi = imageRed.Length / imageRed.GetLength(0);
            int x, y;
            int commonArea = 0;
            int aArea = 0;

            for (y = 0; y < he; y++)
            {
                for (x = 0; x < wi; x++)
                {
                    bool a = imageRed[y,x];
                    bool b = imageBlue[y,x];
                    if (a)
                    {
                        aArea++;
                        if (b)
                        {
                            commonArea++;
                        }
                    }
                }
            }
            //System.out.format("compare2BoolIms(): aArea is %d\n", aArea);
            //System.out.format("compare2BoolIms(): commonArea is %d\n", commonArea);

            double match = (double)commonArea / aArea;
            //System.out.format("compare2BoolIms(): match rate is %.3f\n", match);
            return match;
        }

        public static Shape[] findThinShapes(Shape shape, int n, int outerTryLimit, int innerTryLimit, double thinnessLimit, int areaLowerLimit, int areaHigherLimit)
        {
            Random r = new Random();
            Shape workingShape = (Shape)shape.Clone();
            Shape[] thinShapes = new Shape[n];

            int outerTries = 0;
            int maxFound = 0;
            int i = 0;
            while ((i < n) && (outerTries < outerTryLimit))
            {
                Shape thinShape = null;
                int innerTries = 0;
                while ((thinShape == null) && (innerTries < innerTryLimit))
                {
                    // this may be real slow
                    Point? randomPoint = workingShape.getPoint(r.Next(shape.Set.Count));
                    thinShape = ImageAlgorithms.reduceShapeToAreaAndRatio(workingShape, randomPoint, thinnessLimit, areaLowerLimit, areaHigherLimit);
                    innerTries++;
                }
                if (thinShape == null)
                {
                    outerTries++;
                    i = 0;
                    workingShape = (Shape)shape.Clone();
                    continue;
                }
                thinShapes[i] = thinShape;
                workingShape.subtract(thinShape);
                i++;
                if (i > maxFound)
                {
                    maxFound = i;
                }
            }

            //System.out.format("most thinshapes found: %d\n", maxFound);
            if (outerTries == outerTryLimit)
            {
                //return null;
                return thinShapes;
            }

            return thinShapes;
        }

        public static Shape reduceShapeToAreaAndRatio(Shape originalShape, Point? firstPoint, double ratioLimit, int areaLowerLimit, int areaHigherLimit)
        {
            int[] neighx = { -1, 0, 1, -1, 1, -1, 0, 1 };
            int[] neighy = { -1, -1, 1, 0, 0, 1, 1, 1 };
            if (ratioLimit <= 0)
            {
                return null;
            }
            double ratioLimitInv = 1 / ratioLimit;
            int px = 0, py = 0, nx, ny;
            Point? head, neighbour;



            Shape reducedShape = new Shape();
            int reducedArea = 0;
            double reducedRatio = 1.0;
            Queue<Point?> queue = new Queue<Point?>();
            if (originalShape == null)
            {
                return null;
            }
            if (!originalShape.contains(firstPoint))
            {
                return null;
            }
            queue.Enqueue(firstPoint);
            while (true)
            {
                head = queue.Dequeue();

                reducedShape.Add(head);
                reducedRatio = reducedShape.getAspectRatio();
                reducedArea = reducedShape.Set.Count;
                py = head.Value.Y;
                for (int i = 0; i < 8; i++)
                {
                    nx = px + neighx[i];
                    ny = py + neighy[i];
                    neighbour = new Point(nx, ny);
                    if ((originalShape.contains(neighbour))
                            && (!reducedShape.contains(neighbour)) && (!queue.Contains(neighbour))
                            && (reducedArea + queue.Count < areaHigherLimit))
                    {
                        queue.Enqueue(neighbour);
                    }
                }

                /*
                 * Stop condition. It's weird but it's faster
                 */
                if (!(queue.Count > 0))
                {
                    break;
                }
                else
                {
                    if (reducedArea < areaLowerLimit)
                    {
                        continue;
                    }
                    else if (reducedArea > areaHigherLimit)
                    {
                        break;
                    }
                    else
                    {
                        if ((ratioLimitInv < reducedRatio) && (reducedRatio < ratioLimit))
                        {
                            continue;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            if ((reducedShape.Set.Count < areaLowerLimit) || ((ratioLimitInv < reducedRatio) && (reducedRatio < ratioLimit)))
            {
                return null;
            }
            return reducedShape;
        }

        public static Bitmap boolIm2BuffIm(bool[,] boolImage, int backgroundRGB, int foregroundRGB)
        {
            int x, y;
            int he = boolImage.GetLength(0), wi = boolImage.Length / boolImage.GetLength(0);
            int dRGB;

            Bitmap buffImage = new Bitmap(wi, he);
            for (y = 0; y < he; y++)
            {
                for (x = 0; x < wi; x++)
                {
                    if (boolImage[y,x])
                    {
                        dRGB = foregroundRGB;
                    }
                    else
                    {
                        dRGB = backgroundRGB;
                    }
                    buffImage.SetPixel(x, y, Color.FromArgb(dRGB,dRGB,dRGB));
                }
            }

            return buffImage;
        }

        public static Bitmap grayIntIm2BuffIm(int[,] source)
        {
            int x, y;
            int he = source.GetLength(0), wi = source.Length / source.GetLength(0);            

            Bitmap buffImage = new Bitmap(wi, he);
            for (y = 0; y < he; y++)
            {
                for (x = 0; x < wi; x++)
                {
                    buffImage.SetPixel(x, y, Color.FromArgb(source[y, x], source[y, x], source[y, x]));
                }
            }

            return buffImage;
        }

        public static Bitmap byteIm2BuffIm(byte[] source, int sourceWidth, int sourceHeight)
        {
            int x, y;         

            Bitmap buffImage = new Bitmap(sourceWidth, sourceHeight);
            for (y = 0; y < sourceHeight; y++)
            {
                for (x = 0; x < sourceWidth; x++)
                {
                    int address = 3 * (y * sourceWidth + x);
                    buffImage.SetPixel(x, y, Color.FromArgb(source[address], source[address + 1], source[address + 2]));
                }
            }

            return buffImage;
        }

        public static unsafe Bitmap byteIm2Bitmap2(byte[] bmpBytes, int width, int height)
        {
            Bitmap bmp = new Bitmap(width, height);

            BitmapData bData = bmp.LockBits(new Rectangle(new Point(), bmp.Size),
                ImageLockMode.WriteOnly,
                PixelFormat.Format24bppRgb);

            // Copy the bytes to the bitmap object
            Marshal.Copy(bmpBytes, 0, bData.Scan0, bmpBytes.Length);
            bmp.UnlockBits(bData);
            return bmp;
        }

        public static byte[] boolIm2byteIm(bool[,] boolImage, Color backgroundRGB, Color foregroundRGB)
        {
            int he = boolImage.GetLength(0), wi = boolImage.Length / boolImage.GetLength(0);
            int x, y;

            byte[] byteIm = new byte[wi * he * 3];
            for (y = 0; y < he; y++)
            {
                for (x = 0; x < wi; x++)
                {
                    int address = 3 * (y * wi + x);
                    if (!boolImage[y, x]) {
                        byteIm[address] = backgroundRGB.B;
                        byteIm[address + 1] = backgroundRGB.G;
                        byteIm[address + 2] = backgroundRGB.R;        
                    } else {
                        byteIm[address] = foregroundRGB.B;
                        byteIm[address + 1] = foregroundRGB.G;
                        byteIm[address + 2] = foregroundRGB.R; 
                    }
                }
            }

            return byteIm;
        }

        public static byte[] boolImOverByteIm(bool[,] boolImage, byte[] sourceByteIm, Color foregroundRGB)
        {
            int he = boolImage.GetLength(0), wi = boolImage.Length / boolImage.GetLength(0);
            int x, y;
            
            for (y = 0; y < he; y++)
            {
                for (x = 0; x < wi; x++)
                {
                    if (boolImage[y, x]) {
                        int address = 3 * (y * wi + x);
                        sourceByteIm[address] = foregroundRGB.B;
                        sourceByteIm[address + 1] = foregroundRGB.G;
                        sourceByteIm[address + 2] = foregroundRGB.R;
                    }
                }
            }

            return sourceByteIm;
        }

        public static byte[] boolImHighlightOverByteIm(bool[,] boolImage, byte[] sourceByteIm, Color foregroundRGB, Color highlightRGB)
        {
            int he = boolImage.GetLength(0), wi = boolImage.Length / boolImage.GetLength(0);
            int x, y;

            for (y = 0; y < he; y++)
            {
                for (x = 0; x < wi; x++)
                {
                    int address = 3 * (y * wi + x);
                    bool isBlack = ((sourceByteIm[address] == 0) && (sourceByteIm[address + 1] == 0) && (sourceByteIm[address + 2] == 0));
                    if (boolImage[y, x] && isBlack)
                    {
                        sourceByteIm[address] = foregroundRGB.B;
                        sourceByteIm[address + 1] = foregroundRGB.G;
                        sourceByteIm[address + 2] = foregroundRGB.R;
                    }
                    else if (boolImage[y, x] && !isBlack)
                    {
                        sourceByteIm[address] = highlightRGB.B;
                        sourceByteIm[address + 1] = highlightRGB.G;
                        sourceByteIm[address + 2] = highlightRGB.R;
                    }
                }
            }

            return sourceByteIm;
        }

        public static bool[,] grayIntIm2BoolIm(int[,] grayIntIm, int threshold) {
        if ((grayIntIm == null) || (threshold <= 0)) {
            return null;
        }

        int he = grayIntIm.GetLength(0), wi = grayIntIm.Length / grayIntIm.GetLength(0);
        bool[,] boolIm = new bool[he,wi];
        int sGray;
        int x, y;

        for (y = 0; y < he; y++) {
            for (x = 0; x < wi; x++) {
                sGray = grayIntIm[y,x];

                if (sGray < threshold) {
                    boolIm[y,x] = false;
                } else {
                    boolIm[y,x] = true;
                }
            }
        }

        return boolIm;
    }

        public static bool[,] shape2BoolIm(Shape shape, int wi, int he) {
            bool[,] boolImage = new bool[he, wi];
            foreach (Point? pt in shape.Set)
            {
                boolImage[pt.Value.Y, pt.Value.X] = true;
            }
        
            return boolImage;
        }

    }
}
